/* Sentinel - IRC Statistical and Operator Services
** monserv.c - Monitor service (tcm/oomon) functionality
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: monserv.c,v 1.26 2004/02/16 02:33:43 wcampbel Exp $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "match.h"
#include "s_string.h"
#include "slog.h"

#ifdef MONSERV

static char errstr[BUFSIZE];

static int mn_domain(struct User *, int, char **);
static int mn_host(struct User *, int, char **);
static int mn_userhost(struct User *, int, char **);
static int mn_clones(struct User *, int, char **);
static int mn_help(struct User *, int, char **);
static int mn_cping(struct User *, int, char **);
static int mn_cversion(struct User *, int, char **);
static int mn_drones(struct User *, int, char **);
static int mn_testmask(struct User *, int, char **);
static int mn_scanhost(struct User *, int, char **);

static struct MsgTable monitor_table[] =
  {
    { "HELP", 0, 0, NULL, mn_help },
    { "HOST", 0, 0, NULL, mn_host },
    { "DOMAIN", 0, 0, NULL, mn_domain },
    { "USERHOST", 0, 0, NULL, mn_userhost },
    { "CLONES", 0, 0, NULL, mn_clones },
    { "DRONES", 0, 0, NULL, mn_drones },
    { "TESTMASK", 0, 0, NULL, mn_testmask },
    { "SCANHOST", 0, 0, NULL, mn_scanhost },
    { "\001PING", 0, 0, NULL, mn_cping },
    { "\001VERSION\001", 0, 0, NULL, mn_cversion },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "HOST", "help/monserv/host", 0 },
    { "DOMAIN", "help/monserv/domain", 0 },
    { "USERHOST", "help/monserv/userhost", 0 },
    { "CLONES", "help/monserv/clones", 0 },
    { "DRONES", "help/monserv/drones", 1 },
    { "TESTMASK", "help/monserv/testmask", 1 },
    { "SCANHOST", "help/monserv/scanhost", 1 },
    { (char *)0, (char *)0, 0 }
  };

void monserv(struct User *u, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, param, BUFSIZE);

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (!parc)
    return ;

  if (parv[0][0] != '\001')
    slog(DEFAULT_LOG, L_INFO, "%s received message from %s: %s", MonServ.nick,
         u->nick, buf);

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; monitor_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], monitor_table[i].name))
    {
      status = monitor_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with monserv command %s: %s", parv[0],
             (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  notice(u->nick, settings.name, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int mn_help(struct User *u, int parc, char *parv[])
{
  int i;

  spy("%s:  HELP Request by %s for %s", MonServ.nick, u->nick,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, "%s:  HELP Request by %s for %s",
                  MonServ.nick, u->nick, (parv[1]) ? parv[1] : "NO QUERY");
  if (parc < 2)
  {
    send_help_file(u->nick, settings.name, "help/monserv/index", "index");
    if (IsOper(u))
      send_help_file(u->nick, settings.name, "help/monserv/aindex", "aindex");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      /* I use the myuonly field as an oper only field here */
      if (help_table[i].myuonly)
      {
        if (IsOper(u))
          send_help_file(u->nick, settings.name, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, settings.name, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, settings.name, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, settings.name, "Unknown Help Topic: \2%s\2", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int mn_domain(struct User *u, int parc, char *parv[])
{
  struct HashEnt *he;
  struct User *tu;
  int opercnt = 0;
  dlink_node *dl;
  char domain[MAXHOST];
  char *hostptr = NULL;

  if (parc < 2)
  {
    notice(u->nick, settings.name, "Syntax:  /msg %s DOMAIN <domain>",
            MonServ.nick);
    return 0;
  }

  hostptr = strchr(parv[1], '@');
  if (hostptr == NULL)
    hostptr = parv[1];

  get_domain(domain, hostptr);
  he = findhashent(domainlist, domain);

  if (he == NULL)
  {
    notice(u->nick, settings.name, "No stats for the domain, %s, found.",
            domain);
    return 0;
  }

  DLINK_FOREACH(dl, he->userlist.head)
  {
    tu = (struct User *) dl->data;
    if (IsOper(tu))
      opercnt++;
  }
  notice(u->nick, settings.name, "Domain: %s", domain);
  notice(u->nick, settings.name, "  Cur. clients: %d", he->count);
  notice(u->nick, settings.name, "  Cur. opers  : %d", opercnt);
  return 0;
}

static int mn_host(struct User *u, int parc, char *parv[])
{
  struct HashEnt *he;
  int opercnt = 0;
  struct User *tu;
  dlink_node *dl;

  if (parc < 2)
  {
    notice(u->nick, settings.name, "Syntax:  /msg %s HOST <hostname>",
            MonServ.nick);
    return 0;
  }

  he = findhashent(hostlist, parv[1]);

  if (he == NULL)
  {
    notice(u->nick, settings.name, "No stats for the hostname, %s, found.",
            parv[1]);
    return 0;
  }

  DLINK_FOREACH(dl, he->userlist.head)
  {
    tu = (struct User *) dl->data;
    if (IsOper(tu))
      opercnt++;
  }
  notice(u->nick, settings.name, "Host: %s", parv[1]);
  notice(u->nick, settings.name, "  Cur. clients: %d", he->count);
  notice(u->nick, settings.name, "  Cur. opers  : %d", opercnt);
  return 0;
}

static int mn_userhost(struct User *u, int parc, char *parv[])
{
  struct HashEnt *he;
  int opercnt = 0;
  struct User *tu;
  dlink_node *dl;

  if (parc < 2)
  {
    notice(u->nick, settings.name, "Syntax:  /msg %s USERHOST <hostname>",
            MonServ.nick);
    return 0;
  }

  he = findhashent(uhlist, parv[1]);

  if (he == NULL)
  {
    notice(u->nick, settings.name, "No stats for the user@hostname, %s, found.",
            parv[1]);
    return 0;
  }

  DLINK_FOREACH(dl, he->userlist.head)
  {
    tu = (struct User *) dl->data;
    if (IsOper(tu))
      opercnt++;
  }
  notice(u->nick, settings.name, "Userhost: %s", parv[1]);
  notice(u->nick, settings.name, "  Cur. clients: %d", he->count);
  notice(u->nick, settings.name, "  Cur. opers  : %d", opercnt);
  return 0;
}

static int mn_clones(struct User *u, int parc, char *parv[])
{
  struct HashEnt *he;
  struct User *tu;
  dlink_node *dl;
  int limit = DEFAULT_CLONES_LIMIT;
  int i;

  if (parc >= 2)
    limit = atoi(parv[1]);

  if (limit < 2)
    limit = DEFAULT_CLONES_LIMIT;

  notice(u->nick, settings.name, "Please wait...processing...");

  for (i = 0; i < H_TABLE_SIZE; i++)
  {
    for (he = uhlist[i]; he != NULL; he = he->next)
    {
      if (he->count >= limit)
      {
        notice(u->nick, settings.name, "%s - %d users", he->hashval,
                he->count);
        DLINK_FOREACH(dl, he->userlist.head)
        {
          tu = (struct User *) dl->data;
          notice(u->nick, settings.name, "   %s on %s", tu->nick,
                 tu->server->name);
        }
      }
    }
  }
  notice(u->nick, settings.name, "-- end of list --");
  return 0;
}

static int mn_drones(struct User *u, int parc, char *parv[])
{
#ifdef MON_DRONES
  dlink_node *dl;
  dlink_node *tdl;
  struct DroneNode *dn;

  if (!IsOper(u))
  {
    /* Silently ignore */
    return 0;
  }
  notice(u->nick, settings.name, "Please wait...processing...");
  DLINK_FOREACH_SAFE(dl, tdl, MonServ.drone_users.head)
  {
    dn = dl->data;
    if (IsOper(dn->u))
    {
      RemoveDroneDL(dl);
      continue;
    }
    notice(u->nick, settings.name, "%s!%s@%s on %s reason: %s", dn->u->nick,
            dn->u->username, dn->u->hostname, dn->u->server->name,
            dn->reason);
  }
  notice(u->nick, settings.name, "-- end of list --");
#else

  notice(u->nick, settings.name, "DRONES is disabled");
#endif

  return 0;
}

static int mn_testmask(struct User *u, int parc, char *parv[])
{
  struct User *tu;
  char *username;
  char *hostname;
  int count = 0;
  int i;
  dlink_node *dl;

  if (!IsOper(u))
  {
    /* Silently ignore */
    return 0;
  }

  if (parc < 2)
  {
    notice(u->nick, settings.name, "Syntax:  TESTMASK <user@hostmask>");
    return 0;
  }

  username = parv[1];
  hostname = strchr(username, '@');
  if (hostname == NULL)
  {
    notice(u->nick, settings.name, "Please include a '@' in the mask");
    return 0;
  }
  *hostname = '\0';
  hostname++;

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      tu = dl->data;

      if ((match(username, tu->username) == 0) &&
          (match(hostname, tu->hostname) == 0))
      {
        count++;
      }
    }
  }
  notice(u->nick, settings.name, "%d clients match %s@%s", count, username,
         hostname);
  return 0;
}

/* SCANHOST is contributed by Ilya Shtift <ishtift@tagil.svrw.ru> */
static int mn_scanhost(struct User *source_p, int parc, char *parv[])
{
#ifdef ENABLE_SCANHOST
  struct User *target_p;
  char *username, *hostname;
  int i, count = 0;
  int lim = settings.defaultlimit;
  int broken = FALSE;
  dlink_node *dl;

  if (!IsOper(source_p))
    return 0;

  if (parc < 2)
  {
    notice(source_p->nick, settings.name, "Syntax:  SCANHOST <user@hostmask>");
    return 0;
  }

  username = parv[1];
  hostname = strchr(username, '@');
  if (hostname == NULL)
  {
    notice(source_p->nick, settings.name, "Please include a '@' in the mask");
    return 0;
  }
  *hostname = '\0';
  hostname++;

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      target_p = dl->data;

      if ((match(username, target_p->username) == 0) &&
          (match(hostname, target_p->hostname) == 0))
      {
        count++;
        /* This changes from gate's patch, which was if (count < 50) priv... */
        if (count > lim)
        {
          broken = TRUE;
          break;
        }
        notice(source_p->nick, settings.name, "%s!%s@%s at %s", target_p->nick,
                target_p->username, target_p->hostname, target_p->server->name);
      }
    }
    /* Efficiency check -Hwy */
    if (broken)
      break;
  }
  notice(source_p->nick, settings.name, "%d clients match %s@%s", count,
         username, hostname);
#else

  notice(u->nick, settings.name, "SCANHOST is disabled");
#endif

  return 0;
}

static int mn_cping(struct User *u, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);
  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", MonServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int mn_cversion(struct User *u, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001",
      MonServ.nick, u->nick, stats_version, VersionFlags(), stats_date);
  slog(DEFAULT_LOG, L_INFO, "CTCP VERSION from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

#endif

